import FramingLoad from "./FramingLoad";
import OwnListItem, {
  ItemPos
} from "./OwnListItem";

const {
  ccclass,
  property
} = cc._decorator;

// item模板类型
const ItemTemplateType = cc.Enum({
  Node: 0,
  Prefab: 1,
});

// 添加新item时跳转
const AddItemJump = cc.Enum({
  None: 0, // 不跳
  Top: 1, // 跳到头部
  Bottom: 2, // 跳到尾部
});

// 滚动方向
const enum ScrollDirection {
  Stay,
  Up,
  Down,
};

@ccclass
export default class OwnListView extends cc.Component {
  @property(cc.ScrollView)
  scroll: cc.ScrollView = null;
  @property(FramingLoad)
  framingLoad: FramingLoad = null;
  @property({
    type: ItemTemplateType
  })
  itemTemplateType = ItemTemplateType.Node;
  @property({
    type: cc.Prefab,
    visible: function () {
      const isVisible = this.itemTemplateType === ItemTemplateType.Prefab;
      if (isVisible && this.itemScript.length == 0) {
        this.itemScript = this.itemPrefabTemplate ? this.itemPrefabTemplate.data.name : '';
      }
      return isVisible;
    }
  })
  itemPrefabTemplate: cc.Prefab = null;
  @property({
    type: cc.Node,
    visible: function () {
      const isVisible = this.itemTemplateType === ItemTemplateType.Node;
      if (isVisible && this.itemScript.length == 0) {
        this.itemScript = this.itemTemplate ? this.itemTemplate.name : '';
      }
      return isVisible;
    }
  })
  itemTemplate: cc.Node = null;
  @property
  itemScript: string = '';
  @property({
    tooltip: '不需要设置初始化item数量为0'
  })
  initItemCount: number = 0;
  @property({
    tooltip: 'item不是固定高度时取消勾选'
  })
  isItemFixed: boolean = true;
  @property({
    type: AddItemJump
  })
  addItemJumpTo = AddItemJump.None;
  @property({
    tooltip: 'item最大数量'
  })
  maxItemCount: number = 0;
  @property
  debug: boolean = false;

  private _isInitData: boolean = true; // 是否在初始化数据
  private _getListData: Function = null; // 获取源数据
  private _viewBox: cc.Rect = null; // 缓冲区
  private _itemPool: any = null; // item对象池
  private _preItemNode: cc.Node = null; // 预添加item节点,与view相同
  private _baseContentY: number = 0; // content距离view顶部坐标
  private _lastContentOffsetY: number = 0; // 上次偏移量
  private _lastDataStartIndex: number = 0;
  private _lastDataEndIndex: number = 0;
  private _scrollDir: ScrollDirection = ScrollDirection.Up;
  private _isOperationChild: boolean = false; // 是否在操作节点

  onLoad() {
    // this.scroll.content.on('position-changed', this.updateItems, this);

    // this.initItemTemplate();
  }

  start() {
    const count = this.initItemCount;
    this.framingLoad.executePreFrame(this.initWithItem(count), 1);
  }

  onDestroy() {
    if (this.scroll.content) {
      // this.scroll.content.off('position-changed', this.updateItems, this);
      this.scroll.content.children.forEach(item => {
        this.putItem(item);
      });
    }

    Object.keys(this._itemPool).forEach(key => {
      this.itemScript = key;
      this.curNodePool.clear();
    });
  }

  // 初始化数据开始

  /**
   * @description 初始化列表
   * @param size  初始化列表大小
   * @param getListData 获得列表数据
   */
  initListView(size: cc.Size, getListData: Function) {
    this.initItemTemplate();
    this._isInitData = this.initItemCount > 0;
    this._getListData = getListData;

    this.scroll.node.height = size.height;
    const view = this.scroll.node.getChildByName('view');
    view.height = size.height;
    const viewHeight = this.scroll.content.parent.height;
    this._baseContentY = viewHeight - viewHeight * this.scroll.content.parent.anchorY;
    this.scroll.content.y = this._baseContentY;

    const viewRect: cc.Rect = view.getBoundingBox();
    if (this.isItemFixed) {
      // 固定高度的item缓冲区上下加一
      viewRect.height += this.itemTemplate.height * 4;
      viewRect.y -= this.itemTemplate.height * 2;
    } else {
      // 不固定高度,缓冲区上下加半屏
      viewRect.height += viewRect.height;
      viewRect.y -= viewRect.height / 2;
    }
    this._viewBox = viewRect;
    if (this.debug) this.drawViewBox();

    if (this._isInitData && this.framingLoad) {
      const count = this.initItemCount;
      this.framingLoad.executePreFrame(this.initWithItem(count), 1);
    }
  }

  /**
   * @description 初始化item模板
   */
  initItemTemplate() {
    if (this.itemTemplateType === ItemTemplateType.Prefab) {
      this.itemTemplate = cc.instantiate(this.itemPrefabTemplate);
    }

    this.initItemPool();
  }

  /**
   * @description 初始化对象池
   */
  initItemPool() {
    if (!this._itemPool) this._itemPool = {};
    if (!this.curNodePool) {
      this.curNodePool = new cc.NodePool(this.itemScript);

      if (this.initItemCount == 0) {
        this.putItem(this.itemTemplate);
      } else {
        for (let i = 0; i < this.initItemCount; ++i) {
          this.putItem(this.itemTemplate)
        }
      }
    }
  }

  /**
   * @description 变更item模板
   * @param item item模板
   * @param itemScript item模板脚本名称
   */
  changeItemTemplate(item: cc.Node | cc.Prefab, itemScript: string) {
    if (item instanceof cc.Prefab) {
      this.itemTemplateType = ItemTemplateType.Prefab;
      this.itemPrefabTemplate = item;
    } else {
      this.itemTemplateType = ItemTemplateType.Node;
      this.itemTemplate = item;
    }
    this.itemScript = itemScript;
    this.initItemTemplate();
  }

  // 对象池操作

  /**
   * @description 设置当前对象池
   */
  set curNodePool(nodePool: cc.NodePool) {
    this._itemPool[this.itemScript] = nodePool;
  }

  /**
   * @description 获取当前模板对象池
   */
  get curNodePool(): cc.NodePool {
    return this._itemPool[this.itemScript];
  }

  /**
   * @description 回收节点
   * @param item 待回收节点
   */
  putItem(item: cc.Node) {
    this.curNodePool.put(item);
    if (this.addItemJumpTo === AddItemJump.Bottom) {
      this.scroll.content.height -= item.height;
      this.scroll.content.y = this.scroll.content.height - this.scroll.content.parent.height + this._baseContentY;
      cc.log('putItem', this.scroll.content.y, this.scroll.content.height)
    }
  }

  /**
   * @description 从对象池中取出item
   */
  getItem() {
    let item: cc.Node = null;
    if (this.curNodePool.size() < 1) {
      item = cc.instantiate(this.itemTemplate);
      return item;
    }
    item = this.curNodePool.get();
    return item;
  }

  // 列表操作

  /**
   * @description 设置item数据
   * @param item item节点
   * @param dataIndex 数据索引
   * @param data item数据
   */
  setItemData(item: cc.Node, dataIndex: number, data: any, isPreItem: boolean) {
    item['item_index'] = dataIndex;
    const itemScript = item.getComponent(this.itemScript);
    item.getComponent('OwnListItem').isPreItem = isPreItem;
    if (itemScript['initData']) {
      itemScript.initData({
        ...data,
        removeFromList: (node) => {
          cc.log('put node: ', node['item_index'])
          this.putItem(node);
        }
      });
    }
  }

  /**
   * @description 头部添加item
   * @param dataIndex 数据索引
   */
  addItemToTop(dataIndex: number, isPreItem: boolean = false) {
    const item = this.getItem();
    const data = this._getListData()[dataIndex];
    this.setItemData(item, dataIndex, data, isPreItem);

    if (isPreItem) {
      this.updatePreItemPos(item, true);
    } else {
      this.updateContentPos(item, true);
    }

    cc.log('addItemToTop: ', -1, item['item_index'], item.y)
    this.scroll.content.children.forEach((node, index) => {
      node.y -= node.height;
      cc.log('addItemToTop: ', index, node['item_index'], node.y)
    });
    this.scroll.content.insertChild(item, 0);

    if (this._isInitData) return;
    if (this.addItemJumpTo == AddItemJump.Top) this.scroll.scrollToTop(0.1);
  }

  /**
   * @description 底部添加item
   * @param dataIndex 数据索引
   */
  addItemToBottom(dataIndex: number, isPreItem: boolean = false) {
    const item = this.getItem();
    const data = this._getListData()[dataIndex];
    this.setItemData(item, dataIndex, data, isPreItem);

    if (isPreItem) {
      this.updatePreItemPos(item, false);
    } else {
      this.updateContentPos(item);
    }
    this.scroll.content.addChild(item);

    if (this._isInitData) return;
    if (this.addItemJumpTo == AddItemJump.Bottom) this.scroll.scrollToBottom(0.1);
  }

  /**
   * @description 分帧加载时调用
   * @param count 初始化item数量
   */
  * initWithItem(count: number) {
    const initData = this._getListData();
    const end = Math.max(initData.length - 1, 0);
    const srart = Math.max(end - count, 0);

    for (let i = srart; i < end; ++i) {
      // 最后一条item加载算完成初始化
      this._isInitData = i < end;
      yield this.addItemToBottom(i);
    }
  }

  /**
   * @description 更新新加的item坐标
   * @param item 新添加的item
   * @param isTop 是否头部添加
   */
  updateContentPos(item: cc.Node, isTop: boolean = false) {
    item.x = this.scroll.content.x - this.scroll.content.width * (1 - this.scroll.content.anchorX);
    item.y = this.getTopBottomItemPosY(isTop);
    this.scroll.content.height += item.height;
  }

  getTopBottomItemPosY(isTop: boolean) {
    let y = 0;
    this.scroll.content.children.forEach(item => {
      if (isTop) {
        y = Math.min(y, item.getBoundingBox().yMax);
      } else {
        y = Math.min(y, item.getBoundingBox().yMin);
      }
    });
    return y;
  }

  /**
   * @description 检查缓冲区边缘是否需要添加预缓冲item
   * @param item 缓冲区边缘item
   * @param dataIndex 数据索引
   * @param isTop 是否头部
   */
  checkNeedAddPreItem(item: cc.Node, dataIndex: number, isTop: boolean) {
    const itemScript: OwnListItem = item.getComponent('OwnListItem');
    const itemRect = itemScript.getItemBoundingBox();

    if (isTop) {
      this.addItemToTop(dataIndex, true);
    } else {
      this.addItemToBottom(dataIndex, true);
    }
  }

  /**
   * @description 更新边缘预缓冲item坐标
   * @param preItem 预缓冲item
   * @param isTop 是否头部
   */
  updatePreItemPos(preItem: cc.Node, isTop: boolean) {
    // const view = this.scroll.node.getChildByName('view');
    let pos = cc.Vec2.ZERO;
    if (isTop) {
      const topBox = this.scroll.content.children[0].getBoundingBox();
      pos = topBox.origin.add(cc.v2(0, topBox.height + preItem.height * preItem.anchorY));
    } else {
      const bottomBox = this.scroll.content.children[this.scroll.content.childrenCount - 1].getBoundingBox();
      pos = bottomBox.origin.sub(cc.v2(0, bottomBox.height - preItem.height * preItem.anchorY));
    }
    // const itemPos = view.convertToNodeSpaceAR(pos);
    preItem.x = pos.x;
    preItem.y = pos.y;
  }

  /**
   * @description 滚动list的时候增加进入缓冲区的item
   */
  updateItems() {
    if (!this.scroll.content.childrenCount) return;
    const curConentOffsetY = this.scroll.getScrollOffset().y;
    if (curConentOffsetY == this._lastContentOffsetY &&
      this._lastDataStartIndex == this.scroll.content.children[0]['item_index'] &&
      this._lastDataEndIndex == this.scroll.content.children[this.scroll.content.childrenCount - 1]['item_index']
    ) return;
    const lastIndex = Math.min(this.scroll.content.childrenCount - 1, 0);
    const viewBox = this.scroll.node.getChildByName('view').getBoundingBox();

    if (this._lastContentOffsetY < curConentOffsetY) {
      this._scrollDir = ScrollDirection.Up;
    } else if (this._lastContentOffsetY > curConentOffsetY) {
      this._scrollDir = ScrollDirection.Down;
    } else {
      this._scrollDir = ScrollDirection.Stay;
    }

    this.scroll.content.children.forEach((item, index) => {
      const itemScript: OwnListItem = item.getComponent('OwnListItem');

      const itemRect = itemScript.getItemBoundingBox();
      if (this._viewBox.intersects(itemRect) || this._viewBox.containsRect(itemRect)) {
        // itemScript.enterScrollBuffArea();
        if (viewBox.intersects(itemRect) || viewBox.containsRect(itemRect)) {
          itemScript.enterScrollView();
        } else {
          itemScript.exitScrollView();
        }
      } else {
        if (item['item_index'] == 34) debugger
        itemScript.exitScrollBuffArea();
      }
      // this.checkItemVisible(viewBox, itemScript);
      if (itemScript.getItemPos() == ItemPos.Buff) {
        if (index == 0) {
          if (this._scrollDir === ScrollDirection.Down) {
            const topDataIndex = parseInt(item['item_index']) - 1
            if (topDataIndex >= 0) {
              // this.checkNeedAddPreItem(item, topDataIndex, true);
              this.addItemToTop(topDataIndex, true);
            }
          }
        } else if (index == lastIndex) {
          if (this._scrollDir === ScrollDirection.Up) {
            const bottomDataIndex = parseInt(item['item_index']) + 1;
            if (bottomDataIndex < this._getListData().length) {
              // this.checkNeedAddPreItem(item, item['item_index'], false);
              this.addItemToBottom(bottomDataIndex, true);
            }
          }
        }
      }
    });
    this._lastDataStartIndex = this.scroll.content.children[0]['item_index'];
    this._lastDataEndIndex = this.scroll.content.children[this.scroll.content.childrenCount - 1]['item_index'];
    this._lastContentOffsetY = curConentOffsetY;
  }

  /**
   * @description 更新item位置
   * @param viewBox 可视区域
   * @param itemScript item脚本
   */
  checkItemVisible(viewBox: cc.Rect, itemScript: OwnListItem) {
    const itemRect = itemScript.getItemBoundingBox();
    if (this._viewBox.intersects(itemRect) || this._viewBox.containsRect(itemRect)) {
      // itemScript.enterScrollBuffArea();
      if (viewBox.intersects(itemRect) || viewBox.containsRect(itemRect)) {
        itemScript.enterScrollView();
      } else {
        itemScript.exitScrollView();
      }
    } else {
      itemScript.exitScrollBuffArea();
    }
  }

  update() {
    this.updateItems();
    if (this.debug) this.drawViewBox();
  }

  drawViewBox() {
    let graphic: cc.Graphics = this.node.getComponent(cc.Graphics);
    if (!graphic) {
      this.node.addComponent(cc.Graphics);
      graphic = this.getComponent(cc.Graphics);
    }
    // const rect = this.scroll.node.getChildByName('view').getBoundingBox();
    graphic.clear()

    graphic.strokeColor = cc.color(0, 0, 255);
    graphic.lineWidth = 10;
    graphic.rect(this._viewBox.xMin, this._viewBox.yMin, this._viewBox.width, this._viewBox.height);
    graphic.stroke();

    graphic.fillColor = cc.color(255, 0, 0);
    graphic.circle(this._viewBox.xMin, this._viewBox.yMin, 5);
    graphic.fill();

    graphic.fillColor = cc.color(0, 255, 0);
    graphic.circle(this._viewBox.xMax, this._viewBox.yMax, 5);
    graphic.fill();

    const viewRect = this.scroll.node.getChildByName('view').getBoundingBox();
    graphic.strokeColor = cc.color(255, 255, 0);
    graphic.lineWidth = 10;
    graphic.rect(viewRect.xMin, viewRect.yMin, viewRect.width, viewRect.height);
    graphic.stroke();

    graphic.fillColor = cc.color(255, 0, 0);
    graphic.circle(viewRect.xMin, viewRect.yMin, 5);
    graphic.fill();

    graphic.fillColor = cc.color(0, 255, 0);
    graphic.circle(viewRect.xMax, viewRect.yMax, 5);
    graphic.fill();


    const contentRect = this.scroll.content.getBoundingBox();
    graphic.strokeColor = cc.color(255, 255, 255);
    graphic.lineWidth = 10;
    graphic.rect(contentRect.xMin, contentRect.yMin, contentRect.width, contentRect.height);
    graphic.stroke();

    graphic.fillColor = cc.color(255, 0, 0);
    graphic.circle(contentRect.xMin, contentRect.yMin, 5);
    graphic.fill();

    graphic.fillColor = cc.color(0, 255, 0);
    graphic.circle(contentRect.xMax, contentRect.yMax, 5);
    graphic.fill();
  }
}